package com.sam.lib.store.mmkv;

import android.content.Context;
import android.text.TextUtils;

import com.sam.lib.store.IStore;
import com.tencent.mmkv.MMKV;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


public class MMKVStore implements IStore {

    private final static Map<String, MMKV> map = new HashMap<>();
    private static MMKVStore mStore = null;

    private MMKVStore(Context context, String dir) {
        if (TextUtils.isEmpty(dir)) {
            MMKV.initialize(context.getApplicationContext());
        } else {
            MMKV.initialize(context.getApplicationContext(), dir);
        }
    }

    public static MMKVStore getStore(Context context, String dir) {
        if (mStore == null) {
            synchronized (MMKVStore.class) {
                if (mStore == null) {
                    mStore = new MMKVStore(context, dir);
                }
            }
        }
        return mStore;
    }

    public static MMKVStore getStore(Context context) {
        return getStore(context, null);
    }

    public static MMKV getMMKVWithId(String id, int mode) {
        MMKV target = map.get(id);
        if (target == null) {
            target = MMKV.mmkvWithID(id, mode);
            map.put(id, target);
        }
        return target;
    }

    @Override
    public void putStringSet(Context context, String name, int mode, String key, Set<String> values) {
        getMMKVWithId(name, mode).encode(keyWithType(key, Set.class), values);
    }

    @Override
    public Set<String> getStringSet(Context context, String name, int mode, String key) {
        return getMMKVWithId(name, mode).decodeStringSet(keyWithType(key, Set.class), new HashSet<String>());
    }

    @Override
    public Set<String> getStringSet(Context context, String name, int mode, String key, Set<String> def) {
        return getMMKVWithId(name, mode).getStringSet(keyWithType(key, Set.class), def);
    }

    @Override
    public void putString(Context context, String name, int mode, String key, String value) {
        getMMKVWithId(name, mode).encode(keyWithType(key, String.class), value);
    }

    @Override
    public String getString(Context context, String name, int mode, String key) {
        return getMMKVWithId(name, mode).getString(keyWithType(key, String.class), "");
    }

    @Override
    public String getString(Context context, String name, int mode, String key, String def) {
        return getMMKVWithId(name, mode).getString(keyWithType(key, String.class), def);
    }

    @Override
    public void putLong(Context context, String name, int mode, String key, long value) {
        getMMKVWithId(name, mode).putLong(keyWithType(key, Long.class), value);
    }

    @Override
    public long getLong(Context context, String name, int mode, String key) {
        return getMMKVWithId(name, mode).getLong(keyWithType(key, Long.class), 0L);
    }

    @Override
    public long getLong(Context context, String name, int mode, String key, long def) {
        return getMMKVWithId(name, mode).getLong(keyWithType(key, Long.class), def);
    }

    @Override
    public void putFloat(Context context, String name, int mode, String key, float value) {
        getMMKVWithId(name, mode).putFloat(keyWithType(key, Float.class), value);
    }

    @Override
    public float getFloat(Context context, String name, int mode, String key) {
        return getMMKVWithId(name, mode).getFloat(keyWithType(key, Float.class), 0f);
    }

    @Override
    public float getFloat(Context context, String name, int mode, String key, float def) {
        return getMMKVWithId(name, mode).getFloat(keyWithType(key, Float.class), def);
    }

    @Override
    public void putInt(Context context, String name, int mode, String key, int value) {
        getMMKVWithId(name, mode).putInt(keyWithType(key, Integer.class), value);
    }

    @Override
    public int getInt(Context context, String name, int mode, String key) {
        return getMMKVWithId(name, mode).getInt(keyWithType(key, Integer.class), 0);
    }

    @Override
    public int getInt(Context context, String name, int mode, String key, int def) {
        return getMMKVWithId(name, mode).getInt(keyWithType(key, Integer.class), def);
    }

    @Override
    public void putBoolean(Context context, String name, int mode, String key, boolean value) {
        getMMKVWithId(name, mode).putBoolean(keyWithType(key, Boolean.class), value);
    }

    @Override
    public boolean getBoolean(Context context, String name, int mode, String key) {
        return getMMKVWithId(name, mode).getBoolean(keyWithType(key, Boolean.class), false);
    }

    @Override
    public boolean getBoolean(Context context, String name, int mode, String key, boolean def) {
        return getMMKVWithId(name, mode).getBoolean(keyWithType(key, Boolean.class), def);
    }

    @Override
    public void removeKey(Context context, String name, int mode, String key) {
        removeKeyFromAllType(context, name, mode, key);
    }

    @Override
    public Map<String, ?> getAll(Context context, String name, int mode) {
        return getAll(name, mode);
    }

    @Override
    public void clearAll(Context context, String name, int mode) {
        getMMKVWithId(name, mode).clearAll();
    }


    @Override
    @SuppressWarnings("unchecked")
    public void saveAll(Context context, String name, int mode, Map<String, ?> data) {
        if (data != null && data.size() > 0) {
            for (Map.Entry<String, ?> entry : map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value instanceof Integer) {
                    putInt(context, name, mode, keyWithType(key, Integer.class), (Integer) value);
                } else if (value instanceof Long) {
                    putLong(context, name, mode, keyWithType(key, Long.class), (Long) value);
                } else if (value instanceof Float) {
                    putFloat(context, name, mode, keyWithType(key, Float.class), (Float) value);
                } else if (value instanceof String) {
                    putString(context, name, mode, keyWithType(key, String.class), (String) value);
                } else if (value instanceof Boolean) {
                    putBoolean(context, name, mode, keyWithType(key, Long.class), (Boolean) value);
                } else if (value instanceof Set<?>) {
                    putStringSet(context, name, mode, keyWithType(key, Set.class), (Set<String>) value);
                }
            }
        }
    }

    public final static String KEY_IS_MIGRATE = "_is_migrate";

    @Override
    public void migrate(Context context, String toName, int toMode, IStore fromStore, String fromName, int fromMode) {
        if (!MMKVStore.getMMKVWithId(toName, toMode).getBoolean(KEY_IS_MIGRATE, false)) {
            Map<String, ?> map = fromStore.getAll(context, fromName, fromMode);
            migrateToMMKV(context, toName, toMode, map, MMKVStore.getStore(context));
            MMKVStore.getMMKVWithId(toName, toMode).encode(KEY_IS_MIGRATE, true);
            fromStore.clearAll(context, fromName, fromMode);
        }
    }

    @SuppressWarnings("unchecked")
    private static void migrateToMMKV(Context context, String name, int mode, Map<String, ?> map, MMKVStore store) {
        if (map != null && map.size() > 0 && store != null) {
            Set<String> keys = map.keySet();
            for (String key : keys) {
                Object obj = map.get(key);
                if (obj instanceof String) {
                    store.putString(context, name, mode, key, (String) obj);
                } else if (obj instanceof Boolean) {
                    store.putBoolean(context, name, mode, key, (Boolean) obj);
                } else if (obj instanceof Long) {
                    store.putLong(context, name, mode, key, (Long) obj);
                } else if (obj instanceof Integer) {
                    store.putInt(context, name, mode, key, (Integer) obj);
                } else if (obj instanceof Float) {
                    store.putFloat(context, name, mode, key, (Float) obj);
                } else if (obj instanceof Set<?>) {
                    store.putStringSet(context, name, mode, key, (Set<String>) obj);
                }
            }
        }
    }

    private static String keyWithType(String key, Class<?> clazz) {
        return key + "@" + clazz.getSimpleName();
    }

    public static String getValueType(String keyWithType) {
        if (keyWithType != null && keyWithType.contains("@")) {
            return keyWithType.substring(keyWithType.lastIndexOf("@") + 1);
        }
        return null;
    }

    public static String getKeyWithoutType(String keyWithType) {
        if (keyWithType != null && keyWithType.contains("@")) {
            return keyWithType.substring(0, keyWithType.lastIndexOf("@"));
        }
        return keyWithType;
    }

    private Map<String, ?> getAll(String name, int mode) {
        MMKV mmkv = MMKVStore.getMMKVWithId(name, mode);
        String[] keys = mmkv.allKeys();
        if (keys != null && keys.length > 0) {
            Map<String, Object> map = new HashMap<>(keys.length * 2);
            for (String keyWithType : keys) {
                String key = MMKVStore.getKeyWithoutType(keyWithType);
                String type = MMKVStore.getValueType(keyWithType);
                if (String.class.getSimpleName().equals(type)) {
                    map.put(key, mmkv.decodeString(keyWithType));
                } else if (Integer.class.getSimpleName().equals(type)) {
                    map.put(key, mmkv.decodeInt(keyWithType));
                } else if (Long.class.getSimpleName().equals(type)) {
                    map.put(key, mmkv.decodeLong(keyWithType));
                } else if (Boolean.class.getSimpleName().equals(type)) {
                    map.put(key, mmkv.decodeBool(keyWithType));
                } else if (Float.class.getSimpleName().equals(type)) {
                    map.put(key, mmkv.decodeFloat(keyWithType));
                } else if (Set.class.getSimpleName().equals(type)) {
                    map.put(key, mmkv.decodeStringSet(keyWithType));
                }
            }
            return map;
        }
        return null;
    }

    private static void removeKeyFromAllType(Context context, String name, int mode, String key) {
        MMKVStore.getMMKVWithId(name, mode).remove(keyWithType(key, Integer.class));
        MMKVStore.getMMKVWithId(name, mode).remove(keyWithType(key, Boolean.class));
        MMKVStore.getMMKVWithId(name, mode).remove(keyWithType(key, String.class));
        MMKVStore.getMMKVWithId(name, mode).remove(keyWithType(key, Long.class));
        MMKVStore.getMMKVWithId(name, mode).remove(keyWithType(key, Float.class));
        MMKVStore.getMMKVWithId(name, mode).remove(keyWithType(key, Set.class));
    }

}
